
package trim.differentmaterials.generator.core;

import java.util.*;
import trim.common.*;
import trim.common.utils.*;
import trim.common.inputdata.*;
import trim.differentmaterials.coefficients.*;
import trim.differentmaterials.core.*;
import trim.differentmaterials.generator.columns.*;

/**
 *
 * @author Podorov Alexander
 */

public class Generator
{
    public static final double EPSILON = 1E-10;//mod

    private final int materialCount;
    private final int formatCount;

    private final MaterialStock[] materialStock;
    private final FormatOrder[] formatOrder;

    private final double[] dualVariables;
    private final int[] formatRests;
    private final int[] materialRests;

    private boolean isArtificialMode;

    private final Coefficients coefficients;

    private final List<GapBestPattern> methods;
    private final List<Column> patterns;   

    public Generator(TrimCore core, Coefficients coefficients)
    {
        materialCount = core.getMaterialCount();
        formatCount = core.getFormatCount();

        materialStock = core.getMaterialStock();
        formatOrder = core.getFormatOrder();

        dualVariables = core.getDualVariables();
        formatRests = core.getFormatRests();
        materialRests = core.getMaterialRests();

        methods = new ArrayList<GapBestPattern>();
        patterns = new ArrayList<Column>();

        if (coefficients.getCoefficient(PositiveMaterialGap.class) != null)
        {
            methods.add(new PositiveMaterialGapBestPattern(core, coefficients));
        }
        if (coefficients.getCoefficient(NegativeMaterialGap.class) != null)
        {
            methods.add(new NegativeMaterialGapBestPattern(core, coefficients));
        }
        if (coefficients.getCoefficient(PositiveFormatGap.class) != null)
        {
            methods.add(new PositiveFormatGapBestPattern(core, coefficients));
        }
        if (coefficients.getCoefficient(NegativeFormatGap.class) != null)
        {
            methods.add(new NegativeFormatGapBestPattern(core, coefficients));
        }

        this.coefficients = coefficients;       
    }    

    public void setArtificialMode(boolean isArtificialMode)
    {
        this.isArtificialMode = isArtificialMode;
    }    

    private Column getBestPatternIn(int index, boolean isFiniteFormats)
    {
        Material material = materialStock[index].getMaterial();

        int rest = material.getLength();
        int knifes = material.getKnifeCount();
        int count = 0;       

        double value = 0.0;
        double record = 0.0;        

        int[] items = new int[formatCount];
        int[] bestItems = new int[materialCount + formatCount];

        int[] indexes = new int[formatCount];

        int[] formats = new int[formatCount];

        for (int i = 0; i < formatCount; i++)
        {
            formats[i] = formatOrder[i].getFormat().getLength();
        }

        boolean smallMaterial = true;
        for (int i = 0; i < formatCount; i++)
        {
            if (formats[i] <= material.getLength())
            {
                smallMaterial = false;
                break;
            }
        }

        if (smallMaterial)
        {
            return new RealPattern(bestItems, - Double.MAX_VALUE, coefficients.getCoefficient(RealPattern.class), material);
        }
        
        int[] values = formats;
        
        int dimension = formatCount;

        if (isFiniteFormats)
        {
            int[] temp = new int[formatCount];

            for (int i = 0; i < formatCount; i++)
            {
                if (formatRests[i] == 0)
                {
                    temp[i] = - 1;
                    dimension--;
                }
                else
                {
                    temp[i] = i;
                }
            }

            int next = 0;

            indexes = new int[dimension];

            for (int i = 0; i < formatCount; i++)
            {
                if (temp[i] != - 1)
                {
                    indexes[next] = i;
                    next++;
                }
            }

        }
        else
        {
            indexes = new int[formatCount];
            for (int i = 0; i < formatCount; i++)
            {
                indexes[i] = i;
            }
        }
        
        if (dimension == 1)
        {
            if (isFiniteFormats)
            {
                bestItems[indexes[0]] = Math.min(rest / formats[indexes[0]], formatRests[indexes[0]]);
            }
            else
            {
                bestItems[indexes[0]] = rest / formats[indexes[0]];
            }

            record = values[indexes[0]] * bestItems[indexes[0]];
        }
        else
        {
            int current = - 1;

            boolean exit = false;

            while (!exit)
            {
                while (true)
                {
                    if (current == dimension - 1)
                    {
                        break;
                    }
                    else
                    {
                        current++;
                    }
                    if ((formats[indexes[current]] <= rest) && (items[indexes[current]] == 0))
                    {
                       
                        if (value + rest <= record)
                        {
                            break;
                        }

                        if (isFiniteFormats)
                        {
                            items[indexes[current]] = Math.min(rest / formats[indexes[current]], formatRests[indexes[current]]);
                        }
                        else
                        {
                            items[indexes[current]] = rest / formats[indexes[current]];
                        }

                        rest -= items[indexes[current]] * formats[indexes[current]];
                        value += items[indexes[current]] * values[indexes[current]];
                        count += items[indexes[current]];

                        if ((record < value) && (count <= knifes))
                        {
                            record = value;
                            for (int i = 0; i < dimension; i++)
                            {
                                bestItems[indexes[i]] = items[indexes[i]];
                            }
                        }
                    }
                }

                while (true)
                {
                    current--;

                    if (items[indexes[current]] > 0)
                    {
                        items[indexes[current]]--;
                        rest += formats[indexes[current]];
                        value -= values[indexes[current]];
                        count--;

                        break;
                    }
                    else
                    {
                        if (current == 0)
                        {
                            exit = true;

                            break;
                        }
                    }
                }
            }
        }
        
        bestItems[formatCount + index] = 1;

        return new RealPattern(bestItems, record, coefficients.getCoefficient(RealPattern.class), material);
    }

    private Column getSymplexMethodBestPatternIn(int index)
    {
        int[] formats = new int[formatCount];
        Material material = materialStock[index].getMaterial();

        for (int i = 0; i < formatCount; i++)
        {
            formats[i] = formatOrder[i].getFormat().getLength();
        }

        int[] bestItems = new int[materialCount + formatCount];
        bestItems[formatCount + index] = 1;
        
        int[] items;        

        boolean isUseDualVariables = false;
        for (int i = 0; i < formatCount; i++)
        {
            if (dualVariables[i] > EPSILON)
            {
                isUseDualVariables = true;
                break;
            }
        }

        // threashold might be < 0
        double threshold = Math.abs(dualVariables[formatCount + index] - coefficients.getCoefficient(RealPattern.class).getValue(isArtificialMode));
        
        if (isUseDualVariables)
        {
            boolean isSmallMaterial = true;

            for (int i = 0; i < formatCount; i++)
            {
                if ((dualVariables[i] > EPSILON) && (formats[i] <= material.getLength()))
                {
                    isSmallMaterial = false;
                    break;
                }
            }

            if (isSmallMaterial)
            {
                return new RealPattern(bestItems, - Double.MAX_VALUE, coefficients.getCoefficient(RealPattern.class), material);
            }

            items = Utils.getBestPattern_BB(formats, dualVariables, formatCount, material, threshold);
        }
        else
        {
            double[] values = new double[formatCount];

            boolean isSmallMaterial = true;

            for (int i = 0; i < formatCount; i++)
            {
                if (Math.abs(dualVariables[i]) < EPSILON)
                {
                    values[i] = formats[i];

                    if (formats[i] <= material.getLength())
                    {
                        isSmallMaterial = false;
                    }

                }
                else
                {
                    values[i] = - formats[i];
                }
            }

            if (isSmallMaterial)
            {
                return new RealPattern(bestItems, - Double.MAX_VALUE, coefficients.getCoefficient(RealPattern.class), material);
            }

            items = Utils.getBestPattern_BB(formats, values, formatCount, material, threshold);
        }

        for (int i = 0; i < formatCount; i++)
        {
            bestItems[i] = items[i];
        }

        double record = 0.0;
        for (int i = 0; i < formatCount; i++)
        {
            record += bestItems[i] * dualVariables[i];
        }
        record += bestItems[formatCount + index] * dualVariables[formatCount + index];
        record -= coefficients.getCoefficient(RealPattern.class).getValue(isArtificialMode);

        return new RealPattern(bestItems, record, coefficients.getCoefficient(RealPattern.class), material);
    }
    
    public Column getBestPattern(boolean isFiniteFormats, boolean isFiniteMaterials)
    {
        Column bestPattern = null;
        double minRest = Double.MAX_VALUE;

        for (int i = 0; i < materialCount; i++)
        {
            if ((isFiniteMaterials && (materialRests[i] > 0)) || (!isFiniteMaterials))
            {
                Column pattern = getBestPatternIn(i, isFiniteFormats);
                double tempRest = materialStock[i].getMaterial().getLength() - pattern.getEstimation();

                if ((tempRest < minRest) && (pattern.getEstimation() > 0.0))
                {
                    minRest = tempRest;
                    bestPattern = pattern;
                }
            }
        }

        return bestPattern;
    }

    public Column getSymplexMethodBestPattern()
    {
        Column bestPattern = null;
        double maxEstimation = - Double.MAX_VALUE;

        for (int i = 0; i < materialCount; i++)
        {
            Column pattern = getSymplexMethodBestPatternIn(i);            
            if (pattern.getEstimation() > maxEstimation)
            {
                maxEstimation = pattern.getEstimation();
                bestPattern = pattern;
            }
        }

        return bestPattern;
    }

    public Column getMinimumRestPatternIn(int index)
    {
        return getBestPatternIn(index, false);
    }    

    public Column getResidualPattern(boolean isFiniteMaterials)
    {
        return getBestPattern(true, isFiniteMaterials);
    }

    public Column getSymplexMethodPattern()
    {
        patterns.clear();

        for (GapBestPattern method : methods)
        {
            patterns.add(method.getBestPattern(isArtificialMode));
        }

        Column pattern = getSymplexMethodBestPattern();
        if (pattern != null)
        {
            patterns.add(pattern);
        }        

        Collections.sort(patterns);

        return patterns.get(0);
    }
}
